import serial
import serial.tools.list_ports
import threading
import time
import logging
from datetime import datetime
import sys
import os

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("printer_simulator.log"),
        logging.StreamHandler(sys.stdout),
    ],
)


class PrinterSimulator:
    def __init__(self, port=None, baudrate=115200):
        self.port = port
        self.baudrate = baudrate
        self.serial_conn = None
        self.is_running = False
        self.position = {"X": 0, "Y": 0, "Z": 0, "E": 0}
        self.temperature = {"hotend": 0, "bed": 0}
        self.target_temperature = {"hotend": 0, "bed": 0}
        self.is_heating = False
        self.is_printing = False
        self.fan_speed = 0

    def connect(self):
        """连接到串口"""
        try:
            if self.port is None:
                # 如果没有指定端口，尝试自动查找
                ports = list(serial.tools.list_ports.comports())
                if not ports:
                    logging.error("未找到可用的串口")
                    return False

                # 选择第一个可用的串口
                self.port = ports[0].device
                logging.info(f"自动选择串口: {self.port}")

            self.serial_conn = serial.Serial(
                port=self.port, baudrate=self.baudrate, timeout=1, write_timeout=1
            )

            if self.serial_conn.is_open:
                logging.info(f"成功连接到串口: {self.port}, 波特率: {self.baudrate}")
                return True
            else:
                logging.error(f"无法打开串口: {self.port}")
                return False

        except Exception as e:
            logging.error(f"连接串口时出错: {e}")
            return False

    def disconnect(self):
        """断开串口连接"""
        if self.serial_conn and self.serial_conn.is_open:
            self.serial_conn.close()
            logging.info("已断开串口连接")

    def start(self):
        """启动模拟器"""
        if not self.connect():
            return False

        self.is_running = True

        # 启动读取线程
        read_thread = threading.Thread(target=self._read_loop)
        read_thread.daemon = True
        read_thread.start()

        # 启动温度模拟线程
        temp_thread = threading.Thread(target=self._temperature_simulator)
        temp_thread.daemon = True
        temp_thread.start()

        logging.info("3D打印机模拟器已启动")

        # 发送初始欢迎信息
        self._send_response("start")
        self._send_response("echo: 3D Printer Simulator Ready")

        return True

    def stop(self):
        """停止模拟器"""
        self.is_running = False
        self.disconnect()
        logging.info("3D打印机模拟器已停止")

    def _read_loop(self):
        """读取串口数据的循环"""
        buffer = ""

        while self.is_running and self.serial_conn and self.serial_conn.is_open:
            try:
                # 读取数据
                if self.serial_conn.in_waiting > 0:
                    data = self.serial_conn.read(self.serial_conn.in_waiting).decode(
                        "utf-8", errors="ignore"
                    )
                    buffer += data

                    # 处理完整的行
                    while "\n" in buffer:
                        line, buffer = buffer.split("\n", 1)
                        line = line.strip()

                        if line:
                            logging.info(f"收到: {line}")
                            self._process_command(line)

                time.sleep(0.01)

            except Exception as e:
                logging.error(f"读取串口数据时出错: {e}")
                time.sleep(0.1)

    def _process_command(self, command):
        """处理Gcode命令"""
        # 移除注释和多余空格
        clean_cmd = command.split(";")[0].strip()

        if not clean_cmd:
            return

        # 记录接收到的命令
        logging.info(f"处理命令: {clean_cmd}")

        # 处理M命令 (杂项命令)
        if clean_cmd.startswith("M"):
            self._process_m_command(clean_cmd)

        # 处理G命令 (移动命令)
        elif clean_cmd.startswith("G"):
            self._process_g_command(clean_cmd)

        # 处理特殊命令
        elif clean_cmd == "M105" or clean_cmd.startswith("M105 "):
            self._send_temperature_report()

        elif clean_cmd == "M115":
            self._send_firmware_info()

        elif clean_cmd.startswith("M104") or clean_cmd.startswith("M109"):
            # 设置热端温度
            parts = clean_cmd.split()
            for part in parts:
                if part.startswith("S"):
                    try:
                        temp = float(part[1:])
                        self.target_temperature["hotend"] = temp
                        self._send_response(
                            f"echo: Hotend target temperature set to {temp}"
                        )
                        if temp > 0:
                            self.is_heating = True
                    except ValueError:
                        pass

        elif clean_cmd.startswith("M140") or clean_cmd.startswith("M190"):
            # 设置热床温度
            parts = clean_cmd.split()
            for part in parts:
                if part.startswith("S"):
                    try:
                        temp = float(part[1:])
                        self.target_temperature["bed"] = temp
                        self._send_response(
                            f"echo: Bed target temperature set to {temp}"
                        )
                    except ValueError:
                        pass

        elif clean_cmd.startswith("M106"):
            # 设置风扇速度
            parts = clean_cmd.split()
            for part in parts:
                if part.startswith("S"):
                    try:
                        speed = int(part[1:])
                        self.fan_speed = min(max(speed, 0), 255)
                        self._send_response(f"echo: Fan speed set to {self.fan_speed}")
                    except ValueError:
                        pass

        elif clean_cmd == "M107":
            # 关闭风扇
            self.fan_speed = 0
            self._send_response("echo: Fan turned off")

        elif clean_cmd == "M18" or clean_cmd == "M84":
            # 禁用步进电机
            self._send_response("echo: Steppers disabled")

        # 默认回复ok
        self._send_response("ok")

    def _process_m_command(self, command):
        """处理M命令"""
        # 这里可以添加更多M命令的处理
        pass

    def _process_g_command(self, command):
        """处理G命令"""
        parts = command.split()

        # G0/G1 - 线性移动
        if parts[0] in ["G0", "G1"]:
            for part in parts[1:]:
                if part.startswith("X"):
                    try:
                        self.position["X"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("Y"):
                    try:
                        self.position["Y"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("Z"):
                    try:
                        self.position["Z"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("E"):
                    try:
                        self.position["E"] = float(part[1:])
                    except ValueError:
                        pass

            self._send_response(
                f"echo: Moving to X{self.position['X']} Y{self.position['Y']} Z{self.position['Z']} E{self.position['E']}"
            )

        # G28 - 归位
        elif parts[0] == "G28":
            axes = []
            if len(parts) == 1:
                axes = ["X", "Y", "Z"]
            else:
                for part in parts[1:]:
                    if part in ["X", "Y", "Z"]:
                        axes.append(part)

            for axis in axes:
                self.position[axis] = 0

            self._send_response(f"echo: Homing axes: {', '.join(axes)}")

        # G90 - 绝对定位
        elif parts[0] == "G90":
            self._send_response("echo: Absolute positioning mode")

        # G91 - 相对定位
        elif parts[0] == "G91":
            self._send_response("echo: Relative positioning mode")

        # G92 - 设置位置
        elif parts[0] == "G92":
            for part in parts[1:]:
                if part.startswith("X"):
                    try:
                        self.position["X"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("Y"):
                    try:
                        self.position["Y"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("Z"):
                    try:
                        self.position["Z"] = float(part[1:])
                    except ValueError:
                        pass
                elif part.startswith("E"):
                    try:
                        self.position["E"] = float(part[1:])
                    except ValueError:
                        pass

            self._send_response(
                f"echo: Position set to X{self.position['X']} Y{self.position['Y']} Z{self.position['Z']} E{self.position['E']}"
            )

    def _send_temperature_report(self):
        """发送温度报告"""
        report = f"ok T:{self.temperature['hotend']:.1f} /{self.target_temperature['hotend']:.1f} B:{self.temperature['bed']:.1f} /{self.target_temperature['bed']:.1f} @:127 B@:127"
        self._send_response(report)

    def _send_firmware_info(self):
        """发送固件信息"""
        self._send_response(
            "FIRMWARE_NAME:PrinterSimulator FIRMWARE_URL:https://github.com/simulator FIRMWARE_VERSION:1.0.0"
        )
        self._send_response("MACHINE_TYPE:3D Printer EXTRUDER_COUNT:1")
        self._send_response("ok")

    def _send_response(self, response):
        """发送响应到串口"""
        if self.serial_conn and self.serial_conn.is_open:
            try:
                full_response = response + "\n"
                self.serial_conn.write(full_response.encode("utf-8"))
                logging.info(f"发送: {response}")
            except Exception as e:
                logging.error(f"发送响应时出错: {e}")

    def _temperature_simulator(self):
        """模拟温度变化"""
        while self.is_running:
            # 模拟热端温度变化
            if self.temperature["hotend"] < self.target_temperature["hotend"]:
                self.temperature["hotend"] = min(
                    self.temperature["hotend"] + 2, self.target_temperature["hotend"]
                )
            elif self.temperature["hotend"] > self.target_temperature["hotend"]:
                self.temperature["hotend"] = max(
                    self.temperature["hotend"] - 1, self.target_temperature["hotend"]
                )

            # 模拟热床温度变化
            if self.temperature["bed"] < self.target_temperature["bed"]:
                self.temperature["bed"] = min(
                    self.temperature["bed"] + 1, self.target_temperature["bed"]
                )
            elif self.temperature["bed"] > self.target_temperature["bed"]:
                self.temperature["bed"] = max(
                    self.temperature["bed"] - 0.5, self.target_temperature["bed"]
                )

            time.sleep(1)


def main():
    """主函数"""
    print("=" * 50)
    print("3D打印机串口模拟器")
    print("=" * 50)

    # 显示可用串口
    ports = list(serial.tools.list_ports.comports())
    if ports:
        print("可用串口:")
        for i, port in enumerate(ports):
            print(f"  {i+1}. {port.device} - {port.description}")
    else:
        print("未找到可用串口")

    # 选择串口
    port = None
    if ports:
        try:
            choice = input(
                f"选择串口 (1-{len(ports)}) 或直接输入端口名 [默认: {ports[0].device}]: "
            ).strip()
            if choice:
                if choice.isdigit() and 1 <= int(choice) <= len(ports):
                    port = ports[int(choice) - 1].device
                else:
                    port = choice
            else:
                port = ports[0].device
        except (ValueError, IndexError):
            port = ports[0].device

    # 创建模拟器实例
    simulator = PrinterSimulator(port=port, baudrate=115200)

    try:
        # 启动模拟器
        if simulator.start():
            print("模拟器已启动，按Ctrl+C停止")

            # 主循环
            while True:
                time.sleep(1)
        else:
            print("无法启动模拟器")

    except KeyboardInterrupt:
        print("\n正在停止模拟器...")

    finally:
        simulator.stop()


if __name__ == "__main__":
    main()
